package yui.comn.mybatisx.core.toolkit;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;

/**
 * @author yuyi (1060771195@qq.com)
 */
public class IdWorker {
    protected static final Logger logger = LoggerFactory.getLogger(IdWorker.class);
    
    private final long        workerId;
    private final static long twepoch            = 1288834974657L;
    private long              sequence           = 0L;
    private final static long workerIdBits       = 4L;
    private final static long datacenterIdBits   = 4L;
    public  final static long maxWorkerId        = -1L ^ -1L << workerIdBits;
    private final long        maxDatacenterId    = -1L ^ (-1L << datacenterIdBits);
    private final static long sequenceBits       = 10L;
    private final static long workerIdShift      = sequenceBits;
    private final static long timestampLeftShift = sequenceBits + workerIdBits;
    public  final static long sequenceMask       = -1L ^ -1L << sequenceBits;
    private long              lastTimestamp      = -1L;

    /**
     * 数据标识 ID 部分
     */
    private final long datacenterId;
    
    private static IdWorker idWorker;
    
    public static IdWorker getInstance() {
        if (null == idWorker) {
            idWorker = new IdWorker();
        }
        return idWorker;
    }
    
    public IdWorker() {
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }
    
    public IdWorker(long workerId, long datacenterId) {
        super();
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String
                    .format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String
                    .format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        this.datacenterId = datacenterId;
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = this.timeGen();
        if (this.lastTimestamp == timestamp) {
            this.sequence = (this.sequence + 1) & sequenceMask;
            if (this.sequence == 0) {
                //System.out.println("###########" + sequenceMask);
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            this.sequence = 0;
        }
        if (timestamp < this.lastTimestamp) {
            try {
                throw new Exception(String.format(
                        "Clock moved backwards. Refusing to generate id for %d milliseconds",
                        this.lastTimestamp - timestamp));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        this.lastTimestamp = timestamp;
        long nextId = ((timestamp - twepoch << timestampLeftShift))
                | (this.workerId << workerIdShift) | (this.sequence);
//        System.out.println("timestamp:" + timestamp + ",timestampLeftShift:" + timestampLeftShift
//                + ",nextId:" + nextId + ",workerId:" + workerId + ",sequence:" + sequence);
        return nextId;
    }
    
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        //return System.currentTimeMillis();
        return SystemClock.now();
    }
    

    /**
     * 获取 maxWorkerId
     */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuilder mpid = new StringBuilder();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (StringUtils.isNotBlank(name)) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split(StringPool.AT)[0]);
        }
        /*
         * MAC + PID 的 hashcode 获取16个低位
         */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * 数据标识id部分
     */
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
                    id = id % (maxDatacenterId + 1);
                }
            }
        } catch (Exception e) {
            logger.warn(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }


    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public static void main(String[] args) {
        IdWorker worker2 = new IdWorker();
        IdWorker worker3 = new IdWorker();
        
        System.out.println(worker2.getWorkerId());
        System.out.println(worker3.getWorkerId());
        for (int i=0; i<10000; i++) {
            System.out.println(worker2.nextId() + " - " + worker3.nextId());
            //System.out.println(worker3.nextId());
        }
        // System.out.println(com.baomidou.mybatisplus.core.toolkit.IdWorker.getId());
        // idWorker.getId();
        // for (int i = 0; i < 10; i++) {
            // System.out.println(IdWorker.getInstance().getWorkerId());
            // System.out.println(IdWorker.getInstance().nextId());
            // System.out.println(com.baomidou.mybatisplus.core.toolkit.IdWorker.getId());
        // }
    }

}
